
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;

public class Main extends JFrame {

    public static final int DOWN = 0;
    public static final int LEFT = 1;
    public static final int RIGHT = 2;
    public static final int SPACE = 9;
    public static final int WIDTH = 800;
    public static final int HEIGHT = 600;
    public static final int FIELD_WIDTH = 10 * Polygon.UNIT;
    public static final Color BACKGROUND_COLOR = Color.BLACK;
    public int currentKey = -1;
    private final int GRID_HEIGHT = HEIGHT / Polygon.UNIT;
    private final int[][] GRID = new int[10][GRID_HEIGHT];
    private final Font SCORE_FONT = new Font("TimesRoman", Font.PLAIN, 50);
    private final List POLYGON_LIST;
    private int difficulty = 500;//ms, smaller number is higher difficulty
    private long score;
    private boolean gameOver = false;

    public Main() throws HeadlessException {
        super("TeTrIs");
        this.setSize(WIDTH, HEIGHT);
        this.setBackground(Color.BLACK);
        this.addKeyListener(new Input());
        this.setResizable(false);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        POLYGON_LIST = new ArrayList();
    }

    public void paint(Graphics graphics) {
        Graphics2D graphics2D = (Graphics2D) graphics;
        graphics2D.clearRect(0, 0, WIDTH, HEIGHT);
        for (int a = 0; a < POLYGON_LIST.size(); a++) {
            ((Polygon) POLYGON_LIST.get(a)).draw(graphics2D);
        }
        //border
        graphics2D.setPaint(Color.WHITE);
        graphics2D.drawRect(0, 0, Polygon.UNIT, HEIGHT);
        graphics2D.drawRect(FIELD_WIDTH + Polygon.UNIT, 0, Polygon.UNIT, HEIGHT);

        //score
        graphics2D.setFont(SCORE_FONT);
        graphics2D.drawString("Score: " + score, FIELD_WIDTH + 50, 100);

        if (gameOver) {
            graphics2D.drawString("GAME OVER", FIELD_WIDTH + 150, 300);
        }
    }
    
    public static void main(String[] args) {
        // TODO code application logic here
        Main game = new Main();
        Random random = new Random();
        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'Z'));
        long time = new Date().getTime();
        while (true) {
            if (game.isGameOver()) {
                game.getCurrentPolygon().setInactive();
                game.gameOver = true;
                break;
            }

            //is it time to push polygon down
            if (new Date().getTime() > time + game.difficulty) {
                time = new Date().getTime();
                if (game.isMovementAllowed(DOWN)) {
                    game.getCurrentPolygon().move(DOWN);
                } else {
                    game.getCurrentPolygon().setInactive();
                }
                game.repaint();
            }

            //if current polygon is dead
            if (!game.getCurrentPolygon().isActive()) {
                game.addPolygonToGrid();
                game.removeCompleteLines();
                //get next polygon
                switch (random.nextInt(7)) {
                    case 0:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'I'));
                        break;
                    case 1:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'L'));
                        break;
                    case 2:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'J'));
                        break;
                    case 3:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'Z'));
                        break;
                    case 4:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'T'));
                        break;
                    case 5:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'S'));
                        break;
                    case 6:
                        game.POLYGON_LIST.add(new Polygon(FIELD_WIDTH / 2, 50, new Color(random.nextInt()), 'O'));
                        break;
                }
            }
        }
    }

    public Polygon getCurrentPolygon() {
        return (Polygon) POLYGON_LIST.get(POLYGON_LIST.size() - 1);
    }

    private boolean isMovementAllowed(int direction) {
        for (int a = 0; a < getCurrentPolygon().getCubes().length; a++) {
            Cube temp = getCurrentPolygon().getCubes()[a];
            int x = temp.getUnitX();
            int y = temp.getUnitY();
            switch (direction) {
                case DOWN:
                    if (y >= GRID_HEIGHT || GRID[x - 1][y] == 1) {
                        return false;
                    }
                    break;
                case LEFT:
                    if (x <= 1 || GRID[x - 2][y - 1] == 1) {
                        return false;
                    }
                    break;
                case RIGHT:
                    if (x >= 10 || GRID[x][y - 1] == 1) {
                        return false;
                    }
                    break;
            }
        }
        return true;
    }

    private boolean isRotationInbounds() {
        for (int a = 0; a < getCurrentPolygon().getNewCubes().length; a++) {
            int x = getCurrentPolygon().getNewCubes()[a].getX();
            int y = getCurrentPolygon().getNewCubes()[a].getY();
            if (x < Polygon.UNIT
                    || x > FIELD_WIDTH
                    || y < Polygon.UNIT
                    || y > HEIGHT - Polygon.UNIT
                    || GRID[(x / Polygon.UNIT) - 1][(y / Polygon.UNIT) - 1] == 1) {
                return false;
            }
        }
        return true;
    }

    private void addPolygonToGrid() {
        for (int a = 0; a < getCurrentPolygon().getCubes().length; a++) {
            Cube temp = getCurrentPolygon().getCubes()[a];
            int x = temp.getUnitX();
            int y = temp.getUnitY();
            GRID[x - 1][y - 1] = 1;
        }
    }

    private void removeCompleteLines() {
        int completedLines = 0;
        for (int y = 0; y < GRID[0].length; y++) {
            boolean removeLine = true;
            for (int x = 0; x < GRID.length; x++) {
                if (GRID[x][y] == 0) {//disabled node
                    removeLine = false;
                    break;
                }
                //remove line
            }
            if (removeLine) {
                removeLineAndShiftGrid(y);
                removeLineAndShiftPolygons(y);
                ++completedLines;
            }
        }
        incrementScore(completedLines);
    }

    private void removeLineAndShiftGrid(int lineNumber) {
        for (int y = lineNumber; y >= 0; --y) {
            for (int x = 0; x < GRID.length; x++) {
                if (y > 0) {
                    GRID[x][y] = GRID[x][y - 1];
                } else {
                    GRID[x][0] = 0;
                }
            }
        }
    }

    private void removeLineAndShiftPolygons(int lineNumber) {
        lineNumber *= Polygon.UNIT;
        lineNumber += Polygon.UNIT;
        for (int a = 0; a < POLYGON_LIST.size(); a++) {
            Polygon polygon = (Polygon) POLYGON_LIST.get(a);
            for (int b = 0; b < polygon.getCubes().length; b++) {
                Cube cube = polygon.getCubes()[b];
                if (cube.getY() == lineNumber) {
                    cube.setColor(Color.BLACK);
                } else if (cube.getY() < lineNumber) {
                    cube.setY(cube.getY() + Polygon.UNIT);
                }
            }
        }
        clearDeadPolygons();
    }

    private void clearDeadPolygons() {
        for (int a = 0; a < POLYGON_LIST.size(); a++) {
            boolean dead = true;
            Cube[] cubes = ((Polygon) POLYGON_LIST.get(a)).getCubes();
            for (int b = 0; b < cubes.length; b++) {
                Cube cube = cubes[b];
                if (cube.getColor() != BACKGROUND_COLOR) {
                    dead = false;
                    break;
                }
            }
            if (dead) {
                POLYGON_LIST.remove(a--);
            }
        }
    }

    private void incrementScore(int completedLines) {
        switch (completedLines) {
            case 1:
                score += 10;
                break;
            case 2:
                score += 20;
                break;
            case 3:
                score += 60;
                break;
            case 4:
                score += 240;
                break;
            default:
                break;
        }
    }

    private boolean isGameOver() {
        if (GRID[5][0] == 1) {
            return true;
        }
        return false;
    }

    class Input implements KeyListener {

        private boolean downKey, leftKey, rightKey, rotateKey;

        public void keyTyped(KeyEvent e) {
            //do nothing
        }

        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_DOWN:
                    if (!downKey && isMovementAllowed(DOWN)) {
                        currentKey = DOWN;
                        downKey = true;
                    }
                    break;
                case KeyEvent.VK_LEFT:
                    if (!leftKey && isMovementAllowed(LEFT)) {
                        currentKey = LEFT;
                        leftKey = true;
                    }
                    break;
                case KeyEvent.VK_RIGHT:
                    if (!rightKey && isMovementAllowed(RIGHT)) {
                        currentKey = RIGHT;
                        rightKey = true;
                    }
                    break;
                case KeyEvent.VK_SPACE:
                    if (!rotateKey) {
                        getCurrentPolygon().calculateRotation();
                        if (isRotationInbounds()) {
                            getCurrentPolygon().applyRotation();
                            repaint();
                        } else {
                            getCurrentPolygon().restore();
                        }
                        rotateKey = true;
                    }
                    break;
                /*
                 case KeyEvent.VK_ENTER:
                 getCurrentPolygon().setInactive();
                 repaint();
                 break;
                 */
                case KeyEvent.VK_F12:
                    for (int y = 0; y < GRID[0].length; y++) {
                        for (int x = 0; x < GRID.length; x++) {
                            System.out.print(GRID[x][y]);
                        }
                        System.out.println();
                    }
                    break;
                case KeyEvent.VK_ESCAPE:
                    System.out.println("< good bye >");
                    System.exit(0);
                    break;
            }
            if (currentKey > -1) {
                getCurrentPolygon().move(currentKey);
                repaint();
            }
            currentKey = -1;
        }

        public void keyReleased(KeyEvent e) {
            //reset keys
            downKey = leftKey = rightKey = rotateKey = false;
        }
    }
}
